package smarture;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;

import jnibwapi.JNIBWAPI;
import jnibwapi.model.Unit;
import jnibwapi.types.UnitType.UnitTypes;
import jnibwapi.types.WeaponType;
import lcs.LCS;
import lcs.State;

public class Vulture {
	private final int ACTION_ATTACK = 0;
	private final int ACTION_RETREAT = 1;
	
	private final JNIBWAPI bwapi;
    private final HashSet<Unit> enemyUnits;
    private final Unit unit;
    private final int attackRange;
    private final int maxHealth;
    
    private final int enemyAttackRange;
    private final double enemySpeed;
    
    private long lastTick;
    private long tickDiff;
    private long sleep;
    
    private LCS lcs;
    private int lastAction;
    private State lastState;
    
    private int oldHealth;
    private ArrayList<Integer> oldEnemyHealth;
    
    public Vulture(Unit unit, JNIBWAPI bwapi, HashSet<Unit> enemyUnits) {
        this.unit = unit;
        this.bwapi = bwapi;
        this.enemyUnits = enemyUnits;
        
        this.attackRange = bwapi.getWeaponType(WeaponType.WeaponTypes.Fragmentation_Grenade.getID()).getMaxRange();
        this.enemyAttackRange = bwapi.getWeaponType(WeaponType.WeaponTypes.Psi_Blades.getID()).getMaxRange();
        this.maxHealth = unit.getHitPoints();
        this.enemySpeed = this.bwapi.getUnitType(UnitTypes.Protoss_Zealot.getID()).getTopSpeed();
        this.oldHealth = unit.getHitPoints();
        
        this.lastTick = System.currentTimeMillis();
        
        this.lcs = LCS.load();
        if( this.lcs == null )
        {
        	this.lcs = new LCS();
        	this.lcs.initialize();
        	System.out.println("Creating new LCS");
        }
        this.oldEnemyHealth = new ArrayList<Integer>();
    }
    
    public void step()
    {
    	long tick = System.currentTimeMillis();
    	this.tickDiff = tick - this.lastTick;
    	this.lastTick = tick;
    	
    	if( tick < sleep )
    		return;
    	
    	Unit target = getClosestTarget();
    	if( target == null )
    		return;
    	
    	// get current state
    	State currentState = getState();
    	// get new enemy hitpoints
    	ArrayList<Integer> enemyHealth = new ArrayList<Integer>();
    	for( Unit u : enemyUnits )
    	{
    		enemyHealth.add(u.getHitPoints());
    	}
    	// check if we already did an action
		try {
			double reward = Utils.calculateReward(this.oldHealth, unit.getHitPoints(), this.oldEnemyHealth, enemyHealth);
			if( reward == 0 )
				reward -= 0.1; // we dont wont neutral rewards
			this.lcs.doReward(reward);
			int nextAction = this.lcs.match(currentState);
			this.lcs.step();
	    	this.oldEnemyHealth = enemyHealth;
	    	this.oldHealth = unit.getHitPoints();
	    	
	    	if( nextAction == ACTION_ATTACK )
	    	{
	    		attack();
	    		lastAction = ACTION_ATTACK;
	    		//System.out.println("LCS Attack");
	    	}
	    	else
	    	{
	    		retreat();
	    		lastAction = ACTION_RETREAT;
	    		//System.out.println("LCS Retreat");
	    	}
		} catch (Exception e) {
			e.printStackTrace();
		}
    	lastState = currentState;
    }
    
    private State getState()
    {
    	Unit closestEnemy = this.getClosestTarget();
	
    	int distanceState;
    	double d = getDistance(closestEnemy);
    	d = (this.enemySpeed * this.tickDiff) / d;
    	if( d >= 0.85 )
    		distanceState = 3;
    	else if( d >= 0.55 )
    		distanceState = 2;
    	else if( d >= 0.35 )
    		distanceState = 1;
    	else
    		distanceState = 0;
    	
    	List<Unit> units = this.getTargetsInRange(this.attackRange);
    	
    	int healthState;
    	double health = unit.getHitPoints()/this.maxHealth;
    	if( health >= 0.75 )
    		healthState = 3;
    	else if( health >= 0.5 )
    		healthState = 2;
    	else if( health >= 0.25 )
    		healthState = 1;
    	else
    		healthState = 0;
    	
    	return new State(unit.getGroundWeaponCooldown(),distanceState,units.size(),healthState);
    }
    
    private void attack()
    {
    	Unit target = null;
    	int lowestHealth = Integer.MAX_VALUE;
    	
    	for( Unit u: this.getTargetsInRange(Integer.MAX_VALUE) )
    	{
    		int health = u.getHitPoints();
    		if( target == null || health < lowestHealth )
    		{
    			target = u;
    			lowestHealth = health;
    		}
    	}
    	if( target != null )
    		bwapi.attack(unit.getID(), target.getID());
    }
    
    private boolean retreat( )
    {
    	double[] target = {0,0};
    	for( Unit u : enemyUnits )
    	{
    		if( getDistance(u) < (this.enemyAttackRange+this.enemySpeed*(this.tickDiff+10)) )
    		{
    			target[0] -= (u.getX()-unit.getX());
    			target[1] -= (u.getY()-unit.getY());
    		}
    	}
    	
    	if( target[0] != 0 || target[1] != 0 )
    	{
    		bwapi.move(unit.getID(), (int)(unit.getX()+target[0]), (int)(unit.getY()+target[1]));
    		return true;
    	}
    	return false;
    }
    
    
    private Unit getClosestTarget()
    {
    	Unit result = null;
    	double bestDistance = Double.MAX_VALUE;
    	for( Unit u: enemyUnits )
    	{
    		double distance = getDistance(u);
    		if( result == null || distance < bestDistance )
    		{
    			result = u;
    			bestDistance = distance;
    		}
    	}
    	return result;
    }
    
    List<Unit> getTargetsInRange( double range )
    {
    	List<Unit> result = new LinkedList<Unit>();
    	for( Unit u: enemyUnits )
    	{
    		if( getDistance(u) <= range )
    			result.add(u);
    	}
    	return result;
    }
    
    private double getDistance(Unit u)
    {
    	return Math.sqrt( Math.pow(u.getX()-unit.getX(),2) + Math.pow(u.getY()-unit.getY(),2) );
    }
    
    public int getID()
    {
    	return unit.getID();
    }
    
    public int getHealth()
    {
    	return unit.getHitPoints();
    }
    
    public void saveLCS()
    {
    	this.lcs.save();
    }

}
